/* Copyright © Worklight Ltd. 2006-2011.  All rights reserved. */

package com.worklight.androidgap.plugin;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Enumeration;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.CookieStore;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.protocol.ClientContext;
import org.apache.http.cookie.Cookie;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HttpContext;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.app.ProgressDialog;
import android.os.AsyncTask;
import android.os.Build;
import android.util.Log;
import android.webkit.CookieManager;
import android.webkit.CookieSyncManager;

import com.phonegap.api.Plugin;
import com.phonegap.api.PluginResult;
import com.worklight.androidgap.WLAndroidUtils;
import com.worklight.androidgap.WLDroidGap;
import com.worklight.wlclient.WLConfig;
import com.worklight.wlclient.WLCookieManager;

public class DirectUpdate extends Plugin {
	private static final String ACTION_UPDATE_APP = "updateApp";	
	private static boolean isUpdating = false;

	@Override
	public PluginResult execute(String action, JSONArray args, String callbackId) {
		try {
			PluginResult.Status status = PluginResult.Status.OK;
			JSONObject result = new JSONObject();

			if (ACTION_UPDATE_APP.equals(action)) {
				setUpdating(true);
				String cookies = args.getString(0);
				JSONArray messages = args.getJSONArray(1);
				new AssetsDownloader(cookies, messages).execute(); 		
			} 

			return new PluginResult(status, result);		
		} catch (JSONException e) {
			return new PluginResult(PluginResult.Status.JSON_EXCEPTION, e.getMessage());
		}		
	}


	@Override
	public boolean isSynch(String arg0) {
		return true;
	}

	//////////////////////////////////
	// AssetsDownloader inner class
	//////////////////////////////////

	public static boolean isUpdating() {
		return isUpdating;
	}


	private static void setUpdating(boolean isUpdating) {
		DirectUpdate.isUpdating = isUpdating;
	}

	public class AssetsDownloader extends AsyncTask<Void, Integer, String> {

		private WLDroidGap wlDroidGap;
		private ProgressDialog progressDialog;
		private WLConfig wlConfig;
		private final static String DOWNLOADED_ZIP_FILE_NAME = "assets.zip";
		private boolean isDirectUpdateSuccess = true;
		private String skinName;
		private String cookies;

		// messages
		private String downloadingMessage;
		private static final String MISSING_MESSAGE = "[error: no text]";
		private static final int HONEYCOMB = 11;

		public AssetsDownloader(String cookies, JSONArray messages) {
			super();
			this.wlDroidGap = (WLDroidGap)DirectUpdate.this.ctx;
			this.wlConfig = new WLConfig(this.wlDroidGap);			
			this.cookies = cookies;
			initMessages(messages);
			this.skinName = WLAndroidUtils.readWLPref(this.wlDroidGap, WLDroidGap.SKIN_NAME_PREF_KEY);
		}

		@Override
		protected void onPreExecute() {
			presentProgressDialog();		
		}		

		@Override
		protected String doInBackground(Void... params) {
			runDirectUpdate();
			return null;
		}

		@Override
		protected void onProgressUpdate(Integer... progress) {
			progressDialog.setProgress(progress[0]);
		}

		@Override
		protected void onPostExecute(String result) {
			try {
				if (isDirectUpdateSuccess) {
					CookieSyncManager.createInstance(wlDroidGap);
					CookieManager.getInstance().removeSessionCookie();
					WLAndroidUtils.writeWLPref (DirectUpdate.this.ctx, WLDroidGap.EXIT_ON_SKIN_LOADER, "false");
					wlDroidGap.loadUrl(this.wlDroidGap.getWebMainFilePath());
					progressDialog.dismiss();
				}
			} catch (Throwable e) {
				WLAndroidUtils.error(e.getMessage());
			} finally {
				setUpdating(false);
			}
		}

		private void initMessages(JSONArray args) {
			try {
				downloadingMessage = args.getString(0);
			} catch (JSONException e) {
				downloadingMessage = MISSING_MESSAGE;
				WLAndroidUtils.debug("Failed reading message resource. " + e.toString());
			}
		}

		private void presentProgressDialog(){
			progressDialog = new ProgressDialog(this.wlDroidGap);
			progressDialog.setMessage(downloadingMessage);
			progressDialog.setIndeterminate(false);			
			progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
			//In case the Android version is hiegher then 3.0 we will show the kb - http://bugzilla.worklight.com/show_bug.cgi?id=2647
			if (Build.VERSION.SDK_INT >= HONEYCOMB) {
				try {
					Method setProgressNumberFormatMethod = progressDialog.getClass().getDeclaredMethod("setProgressNumberFormat", String.class);
					setProgressNumberFormatMethod.invoke(progressDialog, "%1d/%2d kb"); 
				} catch (Exception e) {
					WLAndroidUtils.warning("Unable to show units title. " + e.getMessage());
				}
			}
			progressDialog.show();
		}

		private void runDirectUpdate(){
			// downloading the zip file
			try {
				downloadZipFile();			
			}  catch (IOException e) {
				handleUpdateException("directUpdateErrorMessageFailedDownloadingZipFile", "Failed downloading application update file.", Log.ERROR);
				return;
			}

			// proccessing the zip file
			try {
				processZipFile();				
			} catch (IOException e) {
				handleUpdateException("directUpdateErrorMessageFailedProcessingZipFile", "Failed processing application update file.", Log.ERROR);
			}
		}
		
		private void handleUpdateException(String messageKey, String message, int logLevel){
			WLAndroidUtils.log(message, logLevel);
			progressDialog.dismiss();
			isDirectUpdateSuccess = false;
			DirectUpdate.this.webView.loadUrl("javascript:WL.App._showDirectUpdateErrorMessage(" + ((messageKey != null) ? ("WL.ClientMessages." + messageKey) : "\"" + message + "\"") + ")");
		}

		private void downloadZipFile() throws IOException {
			InputStream input = null;
			FileOutputStream output = null;
			try {
				int count;
				HttpEntity responseEntity = sendRequest();
				long lenghtOfFile = responseEntity.getContentLength();				
				progressDialog.setMax((int) (lenghtOfFile / 1024));
				input = new BufferedInputStream(responseEntity.getContent());
				if (responseEntity.getContentType().getValue().equals("application/zip") == false) {
					throw new IOException("The following message has been received from the server instead of the expected application update: " + WLAndroidUtils.convertStreamToString(input));
				}
				WLAndroidUtils.debug("Start copy files to local storage from updated zip file...");
				output = new FileOutputStream(this.wlDroidGap.getLocalStorageRoot() + "/" + DOWNLOADED_ZIP_FILE_NAME);
				byte data[] = new byte[WLAndroidUtils.ANDROID_BUFFER_8K];
				long total = 0;

				while ((count = input.read(data)) != -1) {
					total += count;
					// publishing the progress...
					publishProgress((int) (total / 1024));
					output.write(data, 0, count);
				}
			} finally {
				if (output != null) {
					output.close();
				}
				if (input != null) {
					input.close();
					WLAndroidUtils.debug("Finish copy files to local storage from updated zip file...");
				}
			}
		}	

		private void processZipFile() throws IOException{
			String zipFilePath = this.wlDroidGap.getLocalStorageRoot()+ "/" + DOWNLOADED_ZIP_FILE_NAME;
			extractZipFile(zipFilePath, this.wlDroidGap.getLocalStorageWebRoot());

			// delete zip file
			new File(zipFilePath).delete();

			this.wlDroidGap.setClearCacheNextLoad();
		}

		private void extractZipFile(String zipFilePath, String destFolder) throws IOException{
			// clean old local copy
			WLAndroidUtils.deleteDirectory(new File(destFolder + "/" + this.skinName));
			
			// create root target folder
			new File(destFolder + "/" + this.skinName).mkdirs();			
			
			// extract zip file and copy hierarchy to destFolder
			ZipFile zipFile = new ZipFile(zipFilePath);
			Enumeration<? extends ZipEntry> entries = zipFile.entries();

			while(entries.hasMoreElements()) {
				ZipEntry entry = entries.nextElement();

				if(entry.isDirectory()) {
					File newDirecroty = new File(destFolder + "/" + entry.getName());
					newDirecroty.mkdirs();
					continue;
				}

				InputStream zipEntryStream = zipFile.getInputStream(entry);
				FileOutputStream destFileStream = new FileOutputStream(destFolder + "/" + entry.getName());
				WLAndroidUtils.copyFile(zipEntryStream, destFileStream);
				zipEntryStream.close();
				destFileStream.close();
			}
			zipFile.close();
		}

		private HttpEntity sendRequest() throws ClientProtocolException, IOException{

			// prepare request
			String rootUrl = this.wlConfig.getRootURL().toString();
			String url = rootUrl + "updates?action=getzip&skin=" + this.skinName;
			HttpClient httpclient = new DefaultHttpClient();
			HttpGet request = new HttpGet(url);		


			// set cookie headers
			WLCookieManager.setCookies(this.cookies, this.wlConfig.getRootURL().getHost());
			Set<Cookie> cookies = WLCookieManager.getCookies(); 
			HttpContext httpContext = new BasicHttpContext();
			if (cookies != null) {
				CookieStore cookieStore = new BasicCookieStore();
				for (Cookie cookie : cookies){				
					cookieStore.addCookie(cookie);
				}
				httpContext.setAttribute(ClientContext.COOKIE_STORE, cookieStore);			
			}

			request.addHeader(WLConfig.WL_DOUBLE_COOKIE_HEADER, this.wlConfig.getApplicationVersion());
			Header cookieHeader = WLCookieManager.createCookieHeader(); 
			request.addHeader(cookieHeader);		

			// send request
			HttpResponse response = httpclient.execute(request, httpContext);
			return response.getEntity();
		}
	}
}
